#!/usr/bin/env python
# encoding: utf-8
"""
@author: youfeng
@email: youfeng243@163.com
@license: Apache Licence
@file: proxy_info.py
@time: 2017/8/26 11:53
"""

# 代理相关信息
import json
import time

from tornado import gen

import common
from config import ADSL_TOKEN, KP_USERNAME, KP_PASSWORD


class ProxyInfo(object):
    # 初始化失败
    INIT_FAIL = -1

    # 初始化中...
    INITING = 0

    # 正在被使用的状态
    USING = 1

    # 正在被更新状态
    UPDATING = 2

    # 一个代理最大使用次数
    MAX_USE_COUNT = 200

    # 代理最大超时时间 12个小时必须过期
    MAX_TIME_OUT = 6 * 3600

    # 代理最小生存时间
    MIN_PROXY_TTL = 65

    def __init__(self, index, adsl_host, username, log):
        self.log = log

        # 当前指向自己的索引
        self.point = index

        # 指向可用代理的指针
        self.use_point = index

        # 代理IP信息  haizhi:haizhi@120.11.11.11:8888
        self.proxy = "failed"

        # 代理的账号信息
        self.username = username

        # 代理的拨号信息
        self.adsl_host = adsl_host

        # 代理拨号连接
        self.adsl_url = 'http://{}/re-dial/?token={}&username={}'.format(
            adsl_host, ADSL_TOKEN, username)

        # 获得当前可用鲲鹏代理接口
        self.proxy_url = 'http://{}/un-ip/?token={}&username={}'.format(
            adsl_host, ADSL_TOKEN, username)

        # 开始使用的时间
        self.last_request_time = int(time.time())

        # 已经被使用过的统计信息
        self.use_count = 0

        # 当前代理的使用状态
        self.use_status = self.INITING

        # 初始化代理信息
        self.init_proxy()

    # 指向一个可用的代理。。
    def set_use_point(self, index):
        self.use_point = index

    # 获得可用指针
    def get_use_point(self):
        return self.use_point

    # 初始化代理信息
    def init_proxy(self):

        # 开始使用的时间
        self.last_request_time = int(time.time())

        # 已经被使用过的统计信息
        self.use_count = 0

        text = common.request_get(self.adsl_url, timeout=50)
        if text is None:
            self.log.error("获取某个代理失败: {}".format(self.adsl_url))
            self.use_status = self.INIT_FAIL
            self.last_request_time = int(time.time())
            return

        proxy = self.parse_proxy(text)
        if proxy is None:
            self.log.error("解析代理失败: {} {}".format(self.adsl_url, text))
            self.use_status = self.INIT_FAIL
            self.last_request_time = int(time.time())
            return

        self.proxy = proxy
        self.use_status = self.USING

        # 如果当前代理可用，则指向自己
        self.use_point = self.point

    # 解析可用IP
    def parse_used_proxy(self, text):
        try:
            result = json.loads(text)
            if result is None:
                self.log.error("解析IP信息错误: {} {} {}".format(self.username, self.proxy_url, text))
                return None

            ip = result.get('ip')
            if ip is None or ip == "":
                self.log.error("解析IP信息错误: {} {} {}".format(self.username, self.proxy_url, text))
                return None

            proxy_url = '{}:{}@{}:{}'.format(KP_USERNAME, KP_PASSWORD, ip, 8888)
            self.log.info("获取当前正在使用代理成功: {}".format(proxy_url))
            return proxy_url
        except Exception as e:
            self.log.error("解析代理失败: {}".format(text))
            self.log.exception(e)

        return None

    @gen.coroutine
    def asyc_get_used_proxy(self):
        proxy = None
        try:
            resp = yield common.fetch_coroutine(url=self.proxy_url)
            if resp is None:
                self.log.error("获取IP信息失败, 下次再试: {} {}".format(self.username, self.proxy_url))
                raise gen.Return(proxy)

            # 解析代理
            proxy = self.parse_used_proxy(resp)
            raise gen.Return(proxy)
        except Exception as e:
            self.log.error("获取代理信息异常: {} {}".format(self.username, self.proxy_url))
            self.log.error(e)

        raise gen.Return(proxy)

    # 异步获取IP 信息
    @gen.coroutine
    def asyc_get_proxy(self):
        proxy = None
        is_success = False

        while True:
            try:
                # 这里更新代理信息
                resp = yield common.fetch_coroutine(url=self.adsl_url)
                if resp is None:
                    self.log.error("代理再次初始化失败, 下次再试: {} {}".format(self.username, self.adsl_url))
                    break
            except Exception as e:
                self.log.error("异步初始化代理失败: {} {}".format(self.username, self.adsl_url))
                self.log.exception(e)
                break

            proxy = self.parse_proxy(resp)
            if proxy is None:
                self.log.error("解析代理失败, 下次再重试: {} {}".format(self.username, self.adsl_url))
                break

            is_success = True
            break

        # 判断是否成功..
        if not is_success:
            # 如果拨号失败了 则获得当前正在用的IP
            proxy = yield self.asyc_get_used_proxy()

        raise gen.Return(proxy)

    # 设置正在使用状态...
    def set_in_using(self, proxy):
        # 开始使用的时间
        self.last_request_time = int(time.time())

        # 已经被使用过的统计信息
        self.use_count = 0

        # 初始化代理
        self.proxy = proxy

        self.use_status = self.USING

        # 如果当前代理可用，则指向自己
        self.use_point = self.point

    # 异步初始化
    @gen.coroutine
    def asyc_init_proxy(self):

        # 判断如果最近一次初始化失败的时间 小于一分钟则不进行初始化
        cur_time = int(time.time())
        if cur_time - self.last_request_time < self.MIN_PROXY_TTL:
            self.log.info("当前代理最近一次初始化时间太短，不进行再次初始化: [curtime:{}] [starttime:{}] [username:{}]".format(
                cur_time, self.last_request_time, self.username))
            self.use_status = self.INIT_FAIL
            return

        # 异步获取IP信息
        proxy = yield self.asyc_get_proxy()
        if proxy is None:
            self.use_status = self.INIT_FAIL
            self.last_request_time = int(time.time())
            self.log.error("初始化代理失败: {} {}".format(self.username, self.adsl_url))
            return

        # 设置正在使用状态
        self.set_in_using(proxy)
        self.log.info("重新初始化成功: {} {} {}".format(
            self.adsl_url, self.username, self.proxy))

    # 解析代理
    def parse_proxy(self, text):

        try:
            result_list = json.loads(text)
            item_list = result_list.get('results', None)
            if not isinstance(item_list, list) or len(item_list) <= 0:
                return None
            ip = item_list[0].get('ip', None)
            port = item_list[0].get('proxy_port', None)
            if ip is None or port is None:
                self.log.warn("ip or port is None: [ip:{}] [port:{}]".format(ip, port))
                return None

            if ip == "" or port == "":
                self.log.warn("解析出来的IP 或者port 为空字符串: [adsl_host:{}] [username:{}] [ip:{}] [port:{}]".format(
                    self.adsl_host, self.username, ip, port))
                return None

            proxy_url = '{}:{}@{}:{}'.format(KP_USERNAME, KP_PASSWORD, ip, port)
            self.log.info("获取代理成功: {}".format(proxy_url))

            return proxy_url
        except Exception as e:
            self.log.error("解析代理失败: {}".format(text))
            self.log.exception(e)

        return None

    # 重置代理信息
    @gen.coroutine
    def update_proxy(self):
        if self.use_status == self.UPDATING:
            self.log.info("当前代理已经进入更新状态: {} {}".format(self.adsl_host, self.username))
            return

        # 是否正在初始化
        if self.use_status == self.INITING:
            self.log.info("正在进行初始化，不需要再重入了: {} {}".format(self.adsl_host, self.username))
            return

        # 如果初始化失败了 则再次进行初始化
        if self.use_status == self.INIT_FAIL:
            # 进入初始化状态，设置标志防止重入...
            self.use_status = self.INITING
            yield self.asyc_init_proxy()
            return

        # 比较是否已经超时
        cur_time = int(time.time())
        if self.use_count < self.MAX_USE_COUNT and cur_time - self.last_request_time < self.MAX_TIME_OUT:
            return

        # 如果代理的生存时间少于 最小生存时间，则先不进行获取
        if cur_time - self.last_request_time < self.MIN_PROXY_TTL:
            self.log.warn("代理小于最小生存时间，不能够进行申请: {}".format(self.username))
            return

        self.log.info("开始更新代理: [username:{}] [proxy:{}]".format(self.username, self.proxy))
        self.use_status = self.UPDATING

        # 这里更新代理信息
        # 异步获取IP信息
        proxy = yield self.asyc_get_proxy()
        if proxy is None:
            self.use_status = self.USING
            self.last_request_time = int(time.time())
            self.log.error("更新代理失败: {} {}".format(self.username, self.adsl_url))
            return

        # 设置正在使用状态
        self.set_in_using(proxy)
        self.log.info("更新代理成功: [username:{}] [proxy:{}]".format(self.username, self.proxy))

    # 获得代理
    def get_proxy(self):

        self.use_count += 1

        # 更新代理
        self.update_proxy()

        return self.proxy

    # 判断当前代理是否可用
    def is_using(self):
        return self.point == self.use_point
